package com.fudan.adse.service.impl;

import com.fudan.adse.component.GrpcServiceStub;
import com.fudan.adse.dao.FilmDao;
import com.fudan.adse.dao.FilmRowPieceDao;
import com.fudan.adse.domain.bo.CinemaBO;
import com.fudan.adse.domain.bo.RoomBO;
import com.fudan.adse.domain.bo.SeatBO;
import com.fudan.adse.domain.cont.FilmStatus;
import com.fudan.adse.domain.cont.SeatStatus;
import com.fudan.adse.domain.dto.RowPieceDTO;
import com.fudan.adse.domain.po.*;
import com.fudan.adse.domain.vo.RowPieceWithDetailVO;
import com.fudan.adse.exception.ApiException;
import com.fudan.adse.mbg.mapper.FilmMapper;
import com.fudan.adse.mbg.mapper.FilmRowPieceMapper;
import com.fudan.adse.service.RowPieceService;
import com.fudan.adse.utils.DateTimeUtil;
import com.github.pagehelper.PageHelper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class RowPieceServiceImpl implements RowPieceService {
    private final GrpcServiceStub grpcServiceStub;
    private final FilmRowPieceMapper filmRowPieceMapper;
    private final FilmMapper filmMapper;
    private final FilmDao filmDao;
    private final FilmRowPieceDao filmRowPieceDao;

    @Override
    public FilmRowPiece addRowPiece(RowPieceDTO rowPieceDTO) {
        log.info("开始添加排片信息");
        FilmRowPiece rowPiece = new FilmRowPiece();
        BeanUtils.copyProperties(rowPieceDTO, rowPiece);

        checkAndUpdateRowPiece(rowPiece);
        if (filmRowPieceMapper.insert(rowPiece) > 0) {
            log.info("新排片 {} 添加成功！", rowPiece.getId());
            return rowPiece;
        } else {
            log.info("新排片 {} 添加失败！", rowPiece.getId());
            throw new ApiException("排片失败");
        }
    }

    private void checkAndUpdateRowPiece(FilmRowPiece rowPiece) {
        // 获取影片数据
        Film film = filmMapper.selectByPrimaryKey(rowPiece.getFilmId());
        if (film == null) {
            throw new ApiException("影片不存在");
        }
        if (FilmStatus.DISABLE.getStatus().equals(film.getStatus())) {
            throw new ApiException("影片已下架");
        }
        // 放映截止时间 = 开始放映时间 + 影片时长
        rowPiece.setEndTime(DateTimeUtil.datePlusMinutes(rowPiece.getStartTime(), film.getFootage() / 60));
        // 服务间调用获取影厅数据
        List<RoomBO> rooms = grpcServiceStub.listRoomsByCinemaId(rowPiece.getCinemaId());
        Optional<RoomBO> roomBO = rooms.stream().filter(room -> room.getId().equals(rowPiece.getRoomId())).findFirst();
        if (roomBO.isEmpty()) {
            throw new ApiException("影厅不存在");
        }
        if (Boolean.FALSE.equals(roomBO.get().getStatus())) {
            throw new ApiException("影厅不可用");
        }
        // 剩余容量
        rowPiece.setRemainCapacity(roomBO.get().getCapacity());
        // 验证该时间段内，该影厅是否被占用
        FilmRowPieceExample example1 = new FilmRowPieceExample();
        example1.createCriteria().andCinemaIdEqualTo(rowPiece.getCinemaId())
                .andRoomIdEqualTo(rowPiece.getCinemaId())
                .andStartTimeLessThan(rowPiece.getStartTime())
                .andEndTimeGreaterThan(rowPiece.getStartTime());
        FilmRowPieceExample example2 = new FilmRowPieceExample();
        example2.createCriteria().andCinemaIdEqualTo(rowPiece.getCinemaId())
                .andRoomIdEqualTo(rowPiece.getCinemaId())
                .andStartTimeLessThan(rowPiece.getEndTime())
                .andEndTimeGreaterThan(rowPiece.getEndTime());

        List<FilmRowPiece> rowPieces1 = filmRowPieceMapper.selectByExample(example1);
        List<FilmRowPiece> rowPieces2 = filmRowPieceMapper.selectByExample(example2);
        if (!rowPieces1.isEmpty() || !rowPieces2.isEmpty()) {
            throw new ApiException("该时间段内，该影厅被占用");
        }
        if (rowPiece.getPrice() == null) {
            rowPiece.setPrice((double) 0);
        }
    }

    @Override
    public FilmRowPiece updateRowPiece(RowPieceDTO rowPieceDTO) {
        FilmRowPiece rowPiece = filmRowPieceMapper.selectByPrimaryKey(rowPieceDTO.getId());
        rowPiece.setCinemaId(rowPieceDTO.getCinemaId());
        rowPiece.setFilmId(rowPieceDTO.getFilmId());
        rowPiece.setRoomId(rowPieceDTO.getRoomId());
        rowPiece.setStartTime(rowPieceDTO.getStartTime());
        rowPiece.setPrice(rowPieceDTO.getPrice());

        checkAndUpdateRowPiece(rowPiece);
        if (filmRowPieceMapper.updateByPrimaryKeySelective(rowPiece) < 0) {
            throw new ApiException("更新排片信息失败");
        }
        return rowPiece;
    }

    @Override
    public void deleteRowPiece(Integer id) {
        // todo 如果已经有人买票了怎么处理
        if (filmRowPieceMapper.deleteByPrimaryKey(id) < 0) {
            throw new ApiException("删除排片失败");
        }
    }

    @Override
    public List<RowPieceWithDetailVO> getRowPiecesByFilmId(Integer filmId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        FilmRowPieceExample example = new FilmRowPieceExample();
        example.createCriteria().andFilmIdEqualTo(filmId);
        List<FilmRowPiece> filmRowPieces = filmRowPieceMapper.selectByExample(example);
        Set<Integer> cinemaIds = new HashSet<>();
        for (FilmRowPiece rowPiece : filmRowPieces) {
            Integer cinemaId = rowPiece.getCinemaId();
            cinemaIds.add(cinemaId);
        }
        // 服务间调用，获取 cinema 数据
        Map<Integer, CinemaBO> cinemaBOMap = new HashMap<>();
        grpcServiceStub.listCinemasByIds(new ArrayList<>(cinemaIds))
                .forEach(cinemaBO -> cinemaBOMap.putIfAbsent(cinemaBO.getId(), cinemaBO));
        Map<Integer, RoomBO> roomBOMap = new HashMap<>(8);
        Film film = filmMapper.selectByPrimaryKey(filmId);
        List<RowPieceWithDetailVO> ret = new ArrayList<>();
        for (FilmRowPiece rowPiece : filmRowPieces) {
            grpcServiceStub.listRoomsByCinemaId(rowPiece.getCinemaId()).forEach(roomBO -> roomBOMap.putIfAbsent(roomBO.getId(), roomBO));
            ret.add(RowPieceWithDetailVO.builder()
                    .rowPieceId(rowPiece.getId())
                    .rowPiece(rowPiece)
                    .film(film)
                    .cinema(cinemaBOMap.get(rowPiece.getCinemaId()))
                    .room(roomBOMap.get(rowPiece.getRoomId()))
                    .build());
        }
        return ret;
    }

    @Override
    public List<RowPieceWithDetailVO> getRowPiecesByCinemaId(Integer cinemaId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        FilmRowPieceExample example = new FilmRowPieceExample();
        example.createCriteria().andCinemaIdEqualTo(cinemaId);
        List<FilmRowPiece> filmRowPieces = filmRowPieceMapper.selectByExample(example);
        Set<Integer> filmIds = new HashSet<>();
        for (FilmRowPiece rowPiece : filmRowPieces) {
            filmIds.add(rowPiece.getFilmId());
        }
        Map<Integer, Film> filmMap = new HashMap<>();
        filmDao.selectByFilmIds(new ArrayList<>(filmIds)).forEach(film ->
                filmMap.putIfAbsent(film.getId(), film));
        // 服务间调用，获取 cinema 数据
        CinemaBO cinemaBO = grpcServiceStub.getCinemaById(cinemaId);
        Map<Integer, RoomBO> roomBOMap = new HashMap<>(8);
        grpcServiceStub.listRoomsByCinemaId(cinemaId).forEach(roomBO -> roomBOMap.putIfAbsent(roomBO.getId(), roomBO));
        List<RowPieceWithDetailVO> ret = new ArrayList<>();
        for (FilmRowPiece filmRowPiece : filmRowPieces) {
            ret.add(RowPieceWithDetailVO.builder()
                    .rowPieceId(filmRowPiece.getId())
                    .rowPiece(filmRowPiece)
                    .cinema(cinemaBO)
                    .room(roomBOMap.get(filmRowPiece.getRoomId()))
                    .film(filmMap.get(filmRowPiece.getFilmId()))
                    .build());
        }
        return ret;
    }

    @Override
    public List<RowPieceWithDetailVO> filterByConditions(String title, List<String> types, List<String> statuses,
                                                         Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<RowPieceWithDetailVO> ret = filmRowPieceDao.filterByConditions(title, types, statuses);
        log.info("筛选出{}条排片数据", ret.size());
        Set<Integer> cinemaIds = new HashSet<>();
        for (FilmRowPiece rowPiece : ret.stream().map(RowPieceWithDetailVO::getRowPiece).collect(Collectors.toList())) {
            if (rowPiece != null) {
                cinemaIds.add(rowPiece.getCinemaId());
            }
        }
        // 服务间调用，获取 cinema 数据
        log.info("获取影院信息");
        Map<Integer, CinemaBO> cinemaBOMap = new HashMap<>();
        grpcServiceStub.listCinemasByIds(new ArrayList<>(cinemaIds))
                .forEach(cinemaBO -> cinemaBOMap.putIfAbsent(cinemaBO.getId(), cinemaBO));
        ret.forEach(r -> {
            if (r.getRowPiece() != null) {
                r.setCinema(cinemaBOMap.get(r.getRowPiece().getCinemaId()));
            }
        });
        // 过滤掉影院不可用的数据
        log.info("过滤掉影院不可用的数据");
        ret = ret.stream().filter(vo -> vo.getRowPiece() == null || vo.getCinema().getStatus()).collect(Collectors.toList());
        log.info("获取影厅信息");
        Map<Integer, RoomBO> roomBOMap = new HashMap<>(8);
        ret.forEach(vo -> {
            if (vo.getRowPiece() != null) {
                grpcServiceStub.listRoomsByCinemaId(vo.getCinema().getId()).forEach(roomBO -> roomBOMap.putIfAbsent(roomBO.getId(), roomBO));
                vo.setRoom(roomBOMap.get(vo.getRowPiece().getRoomId()));
            }
        });
        log.info("共{}条数据", ret.size());
        return ret;
    }

    @Override
    public RowPieceWithDetailVO getRowPieceWithSeats(Integer rowPieceId) {
        FilmRowPiece rowPiece = filmRowPieceMapper.selectByPrimaryKey(rowPieceId);
        if (rowPiece == null) {
            throw new ApiException("排片场次不存在");
        }
        Film film = filmMapper.selectByPrimaryKey(rowPiece.getFilmId());
        if (FilmStatus.DISABLE.getStatus().equals(film.getStatus())) {
            throw new ApiException("影片不可访问：已下架");
        }
        CinemaBO cinema = grpcServiceStub.getCinemaById(rowPiece.getCinemaId());
        if (Boolean.FALSE.equals(cinema.getStatus())) {
            throw new ApiException("影院不可访问：已关闭");
        }
        // 服务间调用，获取 cinema 数据
        RowPieceWithDetailVO ret = RowPieceWithDetailVO.builder()
                .rowPieceId(rowPieceId)
                .rowPiece(rowPiece)
                .film(film)
                .cinema(cinema)
                .build();
        List<RoomBO> roomBOList = grpcServiceStub.listRoomsByCinemaId(rowPiece.getCinemaId());
        List<SeatBO> seats = new ArrayList<>();
        for (RoomBO roomBO : roomBOList) {
            if (roomBO.getId().equals(rowPiece.getRoomId())) {
                int capacity = roomBO.getCapacity();
                // 根据订单获取已售出位置（也可能包括退票后空闲的位置）
                List<SeatBO> soldSeats = grpcServiceStub.listSeatsByRowPieceId(rowPieceId);
                soldSeats.sort(Comparator.comparing(SeatBO::getId));
                int idx = 0;
                for (int j = 1; j <= capacity; j++) {
                    if (idx < soldSeats.size() && soldSeats.get(idx).getId() == j) {
                        seats.add(soldSeats.get(idx++));
                    } else {
                        SeatBO seatBO = new SeatBO();
                        seatBO.init(j, rowPiece.getId(), SeatStatus.FREE);
                        seats.add(seatBO);
                    }
                }
                ret.setRoom(roomBO);
            }
        }
        ret.setSeats(seats);
        ret.setCapacity(seats.size());
        return ret;
    }
}
